import { Renderer } from "./Renderer.js";
import { TextRenderer } from "./TextRenderer.js";
import { Slugger } from "./Slugger.js";
import { defaults } from "./defaults.js";
import { unescape } from "./helpers.js";

/**
 * Parsing & Compiling
 */
export class Parser {
	constructor(options) {
		this.options = options || defaults;
		this.options.renderer = this.options.renderer || new Renderer();
		this.renderer = this.options.renderer;
		this.renderer.options = this.options;
		this.textRenderer = new TextRenderer();
		this.slugger = new Slugger();
	}

	/**
	 * Static Parse Method
	 */
	static parse(tokens, options) {
		const parser = new Parser(options);
		return parser.parse(tokens);
	}

	/**
	 * Static Parse Inline Method
	 */
	static parseInline(tokens, options) {
		const parser = new Parser(options);
		return parser.parseInline(tokens);
	}

	/**
	 * Parse Loop
	 */
	parse(tokens, top = true) {
		let out = "",
			i,
			j,
			k,
			l2,
			l3,
			row,
			cell,
			header,
			body,
			token,
			ordered,
			start,
			loose,
			itemBody,
			item,
			checked,
			task,
			checkbox,
			ret;

		const l = tokens.length;
		for (i = 0; i < l; i++) {
			token = tokens[i];

			// Run any renderer extensions
			if (
				this.options.extensions &&
				this.options.extensions.renderers &&
				this.options.extensions.renderers[token.type]
			) {
				ret = this.options.extensions.renderers[token.type].call(
					{ parser: this },
					token
				);
				if (
					ret !== false ||
					![
						"space",
						"hr",
						"heading",
						"code",
						"table",
						"blockquote",
						"list",
						"html",
						"paragraph",
						"text"
					].includes(token.type)
				) {
					out += ret || "";
					continue;
				}
			}

			switch (token.type) {
				case "space": {
					continue;
				}
				case "hr": {
					out += this.renderer.hr();
					continue;
				}
				case "heading": {
					out += this.renderer.heading(
						this.parseInline(token.tokens),
						token.depth,
						unescape(this.parseInline(token.tokens, this.textRenderer)),
						this.slugger
					);
					continue;
				}
				case "code": {
					out += this.renderer.code(token.text, token.lang, token.escaped);
					continue;
				}
				case "table": {
					header = "";

					// header
					cell = "";
					l2 = token.header.length;
					for (j = 0; j < l2; j++) {
						cell += this.renderer.tablecell(
							this.parseInline(token.header[j].tokens),
							{ header: true, align: token.align[j] }
						);
					}
					header += this.renderer.tablerow(cell);

					body = "";
					l2 = token.rows.length;
					for (j = 0; j < l2; j++) {
						row = token.rows[j];

						cell = "";
						l3 = row.length;
						for (k = 0; k < l3; k++) {
							cell += this.renderer.tablecell(this.parseInline(row[k].tokens), {
								header: false,
								align: token.align[k]
							});
						}

						body += this.renderer.tablerow(cell);
					}
					out += this.renderer.table(header, body);
					continue;
				}
				case "blockquote": {
					body = this.parse(token.tokens);
					out += this.renderer.blockquote(body);
					continue;
				}
				case "list": {
					ordered = token.ordered;
					start = token.start;
					loose = token.loose;
					l2 = token.items.length;

					body = "";
					for (j = 0; j < l2; j++) {
						item = token.items[j];
						checked = item.checked;
						task = item.task;

						itemBody = "";
						if (item.task) {
							checkbox = this.renderer.checkbox(checked);
							if (loose) {
								if (
									item.tokens.length > 0 &&
									item.tokens[0].type === "paragraph"
								) {
									item.tokens[0].text = checkbox + " " + item.tokens[0].text;
									if (
										item.tokens[0].tokens &&
										item.tokens[0].tokens.length > 0 &&
										item.tokens[0].tokens[0].type === "text"
									) {
										item.tokens[0].tokens[0].text =
											checkbox + " " + item.tokens[0].tokens[0].text;
									}
								} else {
									item.tokens.unshift({
										type: "text",
										text: checkbox
									});
								}
							} else {
								itemBody += checkbox;
							}
						}

						itemBody += this.parse(item.tokens, loose);
						body += this.renderer.listitem(itemBody, task, checked);
					}

					out += this.renderer.list(body, ordered, start);
					continue;
				}
				case "html": {
					// TODO parse inline content if parameter markdown=1
					out += this.renderer.html(token.text);
					continue;
				}
				case "paragraph": {
					out += this.renderer.paragraph(this.parseInline(token.tokens));
					continue;
				}
				case "text": {
					body = token.tokens ? this.parseInline(token.tokens) : token.text;
					while (i + 1 < l && tokens[i + 1].type === "text") {
						token = tokens[++i];
						body +=
							"\n" +
							(token.tokens ? this.parseInline(token.tokens) : token.text);
					}
					out += top ? this.renderer.paragraph(body) : body;
					continue;
				}

				default: {
					const errMsg = 'Token with "' + token.type + '" type was not found.';
					if (this.options.silent) {
						console.error(errMsg);
						return;
					} else {
						throw new Error(errMsg);
					}
				}
			}
		}

		return out;
	}

	/**
	 * Parse Inline Tokens
	 */
	parseInline(tokens, renderer) {
		renderer = renderer || this.renderer;
		let out = "",
			i,
			token,
			ret;

		const l = tokens.length;
		for (i = 0; i < l; i++) {
			token = tokens[i];

			// Run any renderer extensions
			if (
				this.options.extensions &&
				this.options.extensions.renderers &&
				this.options.extensions.renderers[token.type]
			) {
				ret = this.options.extensions.renderers[token.type].call(
					{ parser: this },
					token
				);
				if (
					ret !== false ||
					![
						"escape",
						"html",
						"link",
						"image",
						"strong",
						"em",
						"codespan",
						"br",
						"del",
						"text"
					].includes(token.type)
				) {
					out += ret || "";
					continue;
				}
			}

			switch (token.type) {
				case "escape": {
					out += renderer.text(token.text);
					break;
				}
				case "html": {
					out += renderer.html(token.text);
					break;
				}
				case "link": {
					out += renderer.link(
						token.href,
						token.title,
						this.parseInline(token.tokens, renderer)
					);
					break;
				}
				case "image": {
					out += renderer.image(token.href, token.title, token.text);
					break;
				}
				case "strong": {
					out += renderer.strong(this.parseInline(token.tokens, renderer));
					break;
				}
				case "em": {
					out += renderer.em(this.parseInline(token.tokens, renderer));
					break;
				}
				case "codespan": {
					out += renderer.codespan(token.text);
					break;
				}
				case "br": {
					out += renderer.br();
					break;
				}
				case "del": {
					out += renderer.del(this.parseInline(token.tokens, renderer));
					break;
				}
				case "text": {
					out += renderer.text(token.text);
					break;
				}
				default: {
					const errMsg = 'Token with "' + token.type + '" type was not found.';
					if (this.options.silent) {
						console.error(errMsg);
						return;
					} else {
						throw new Error(errMsg);
					}
				}
			}
		}
		return out;
	}
}
